﻿//-----------------------------------------------------------------------------
// <copyright file="CommandToButtonsMap.cs" company="William E. Kempf">
//     Copyright (c) Peter O'Hanlon.
// </copyright>
//-----------------------------------------------------------------------------

namespace Onyx.Command
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Controls.Primitives;
    using System.Windows.Input;

    /// <summary>
    /// Maintains a mapping between ICommand and ButtonBase
    /// objects, for the ButtonBaseExtensions class.
    /// </summary>
    internal class CommandToButtonsMap
    {
        /// <summary>
        /// Maps ICommand objects to lists of ButtonBase objects.  Stores the object references
        /// as WeakReferences, so that the commands and buttons can be garbage collected as necessary.
        /// </summary>
        private readonly Dictionary<WeakReference, List<WeakReference>> map = new Dictionary<WeakReference, List<WeakReference>>();

        /// <summary>
        /// Adds the button to the map.
        /// </summary>
        /// <param name="button">The button.</param>
        /// <param name="command">The command.</param>
        public void AddButtonToMap(ButtonBase button, ICommand command)
        {
            if (!this.ContainsCommand(command))
            {
                this.map.Add(new WeakReference(command), new List<WeakReference>());
            }

            List<WeakReference> weakRefs = this.GetButtonsFromCommand(command);
            weakRefs.Add(new WeakReference(button));
        }

        /// <summary>
        /// Determines whether the map contains the specified command.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <returns>
        /// <see langword="true"/> if the map contains the specified command; otherwise, <see langword="false"/>.
        /// </returns>
        public bool ContainsCommand(ICommand command)
        {
            return this.GetButtonsFromCommand(command) != null;
        }

        /// <summary>
        /// Iterates the commands in the map and executes the <paramref name="callback"/>.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <param name="callback">The callback.</param>
        public void ForEachButton(ICommand command, Action<ButtonBase> callback)
        {
            List<WeakReference> buttonRefs = this.GetButtonsFromCommand(command);
            for (int i = buttonRefs.Count - 1; i > -1; --i)
            {
                WeakReference weakRef = buttonRefs[i];
                ButtonBase btn = weakRef.Target as ButtonBase;
                if (btn != null)
                {
                    callback(btn);
                }
            }
        }

        /// <summary>
        /// Removes the button from the map.
        /// </summary>
        /// <param name="button">The button.</param>
        /// <param name="command">The command.</param>
        public void RemoveButtonFromMap(ButtonBase button, ICommand command)
        {
            List<WeakReference> buttonRefs = this.GetButtonsFromCommand(command);
            if (buttonRefs == null)
            {
                return;
            }

            for (int i = buttonRefs.Count - 1; i > -1; --i)
            {
                WeakReference weakRef = buttonRefs[i];
                if (weakRef.Target == button)
                {
                    buttonRefs.RemoveAt(i);
                    break;
                }
            }
        }

        /// <summary>
        /// Gets the buttons from command.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <returns>The buttons.</returns>
        private List<WeakReference> GetButtonsFromCommand(ICommand command)
        {
            this.Prune();
            return this.map.FirstOrDefault(entry => entry.Key.Target == command).Value;
        }

        /// <summary>
        /// Prunes this instance.
        /// </summary>
        private void Prune()
        {
            List<WeakReference> commands = this.map.Keys.ToList();
            for (int cmdIndex = commands.Count - 1; cmdIndex > -1; --cmdIndex)
            {
                WeakReference cmdRef = commands[cmdIndex];
                if (!cmdRef.IsAlive)
                {
                    this.map.Remove(cmdRef);
                }
                else
                {
                    List<WeakReference> buttons = this.map[cmdRef];
                    for (int index = buttons.Count - 1; index > -1; --index)
                    {
                        WeakReference btnRef = buttons[index];
                        if (!btnRef.IsAlive)
                        {
                            buttons.RemoveAt(index);
                        }
                    }
                }
            }
        }
    }
}
